package game;

import static game.Configurations.SCREEN_HEIGHT;
import static game.Configurations.SCREEN_WIDTH;
import game.Controls.Key;
import static game.Controls.Key.*;
import static game.TextManager.t;
import images.BlockLoader;
import images.ComponentLoader;
import images.ImageInfo;
import images.ImageManager;
import images.ItemLoader;
import images.MenuLoader;
import images.ObjectLoader;
import images.ShipLoader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import math.Point;
import menu.*;
import org.apache.commons.io.FileUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;
import org.newdawn.slick.Color;
import saves.SaveManager;
import screen.BoxTexto;
import screen.CaixaTexto;
import screen.Lista;
import screen.ListaImagem;
import screen.ListaTexto;
import screen.ObjetoTela;
import screen.Texto;

public final class Game {

    private int status;
    private long lastFrame;
    
    private ArrayList<ObjetoTela> objetosTela;
    private Lista lista;
    
    private SaveManager gSaves;
    private Menu currentMenu; //The in-game menu being seem at the moment; null if not in menu mode
    
    private HashMap<Integer, Integer> keyTiming;
    
    private Point pf; //Posicao fundo
    private double vp; //Velocidade fundo
    
    public static final String CONTROLS_FILE = "key.bindings", TEXT_FOLDER = "languages", IMAGES_FOLDER = "images", SAVES_FOLDER = "saves", DATA_FOLDER = "data", FONTS_FOLDER = "fonts";

    public static void loadConfigs() {
        //Load configs from Java's Preferences API
        Configurations.load();
        Configurations.reset();

        //Check if DATA_FOLDER exists
        String data = Configurations.getDataPath();
        File df = new File(data);
        if (!df.exists()) {
            df.mkdirs();
            //Copy default contents
            try {
                FileUtils.copyDirectory(new File(TEXT_FOLDER), new File(data + "\\" + TEXT_FOLDER));
                FileUtils.copyDirectory(new File(IMAGES_FOLDER), new File(data + "\\" + IMAGES_FOLDER));
                FileUtils.copyDirectory(new File(DATA_FOLDER), new File(data + "\\" + DATA_FOLDER));
                FileUtils.copyDirectory(new File(FONTS_FOLDER), new File(data + "\\" + FONTS_FOLDER));
            } catch (IOException ex) {
                System.err.println("There was an error while creating the data folder. Error: " + ex.getLocalizedMessage());
            }

            //Create SAVES folder
            new File(data + "\\" + SAVES_FOLDER).mkdir();
        }

        Controls.load(data + "\\" + CONTROLS_FILE);
        TextManager.load(data + "\\" + TEXT_FOLDER);
        ImageManager.load(data + "\\" + IMAGES_FOLDER);
        FontLoader.PATH = data + "\\" + FONTS_FOLDER;
        SaveManager.SAVE_PATH = data + "\\" + SAVES_FOLDER + "\\";
        Data.CAMINHO = data + "\\" + DATA_FOLDER + "\\";
    }

    public static void main(String[] args) {
        loadConfigs();
        new Game();
    }

    public static void initGL(int l, int a) {
        try {
            Display.setDisplayMode(new DisplayMode(l, a));
            Display.setTitle(t().NAME);
            Display.create();
        } catch (LWJGLException e) {
            e.printStackTrace();
        }

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, l, a, 0, 1, -1);        
        glMatrixMode(GL_MODELVIEW);

        glEnable(GL_TEXTURE_2D);
        glDisable(GL_DEPTH_TEST);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        
        ImageManager.setupLights();
    }

    public static void load() {
        loadInfo();
        loadImages();
        MenuData.load();
    }

    public static void loadInfo() {
        Data.load();
    }

    public static void loadImages() {
        try {
            BlockLoader.load();
            ObjectLoader.load();
            ComponentLoader.load();
            MenuLoader.load();
            ItemLoader.load();

            ShipLoader.load();
            
            //shaders
            ShipLoader.setupShaders("ship.frag");
            ImageManager.setupGray("gray.frag");

            FontLoader.load();
        } catch (FileNotFoundException e) {
            System.err.println("File not found: " + e.getLocalizedMessage());
            Display.destroy();
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Unknown error while reading images file: " + e.getLocalizedMessage());
            Display.destroy();
            System.exit(1);
        }
    }

    public Game() {
        initGL(SCREEN_WIDTH, SCREEN_HEIGHT);
        load();
        gSaves = new SaveManager();

        lastFrame = getTime();
        setStatus(0);
        pf = new Point(SCREEN_WIDTH / 3 - ObjectLoader.BACK.getImageWidth() / 2, 200);
        vp = 0.01;
        keyTiming = new HashMap<>();

        while (!Display.isCloseRequested()) {
            int delta = getDelta();
            checarMovimentos(delta);
            if (status == -1)
                break;
            atualizar(delta);

            glClear(GL_COLOR_BUFFER_BIT);
            glColor3f(1f, 1f, 1f);
            desenhar();
            Display.update();
            Display.sync(60);
        }

        Configurations.save();
        Display.destroy();
        System.exit(0);
    }

    public void setStatus(int novoStatus) {
        status = novoStatus;
        switch (status) {
            case 0: //Main SimpleMenu
                objetosTela = new ArrayList<>();
                objetosTela.add(new Texto(Texto.CENTRALIZADO, 10, t().NAME, FontLoader.MAIN_TITLE, Color.yellow));
                lista = new ListaTexto(Texto.CENTRALIZADO, 60, t().MENU_OPTIONS);
                break;
            case 1: //Play
                objetosTela = new ArrayList<>();
                objetosTela.add(new Texto(Texto.CENTRALIZADO, 10, t().MENU_OPTIONS[0], FontLoader.TITLE, Color.yellow));
                String[] ls = gSaves.listaSaves();
                this.lista = new ListaTexto(Texto.CENTRALIZADO, 60, ls);
                this.lista.setSelecao(gSaves.selectedSave(ls));
                break;
            case 2: //Extras
                objetosTela = new ArrayList<>();
                lista = null;
                break;
            case 3: //Options
                objetosTela = new ArrayList<>();
                lista = null;
                break;
            case 4: //Novo Jogo: Nome
                objetosTela = new ArrayList<>();
                objetosTela.add(new CaixaTexto(150, 80, "", 250));
                objetosTela.add(new Texto(Texto.CENTRALIZADO, 10, t().NEW_GAME[0], FontLoader.TITLE, Color.yellow));
                objetosTela.add(new Texto(10, 75, t().NEW_GAME[1], FontLoader.MENU, Color.white));
                lista = null;
                break;
            case 5: //Novo Jogo: Nave
                String nome = ((CaixaTexto) objetosTela.get(0)).getTexto();
                if (nome.isEmpty())
                    //error! todo dialog ?
                    System.err.println("Nome inválido!");
                objetosTela = new ArrayList<>();
                objetosTela.add(new Texto(Texto.CENTRALIZADO, 10, t().NEW_GAME[2], FontLoader.TITLE, Color.yellow));
                objetosTela.add(new Texto(10, 75, t().NEW_GAME[1], FontLoader.MENU, Color.white));
                objetosTela.add(new Texto(150, 75, nome, FontLoader.MENU, Color.white));
                objetosTela.add(new BoxTexto(10, 300, SCREEN_WIDTH - 20, t().SHIPS_DESCRIPTION[0]));
                lista = new ListaImagem(216, 150, 128, 128, null, new ImageInfo[3]) {
                    @Override
                    public void desenhar() {
                        super.desenhar();
                        //desenhar as naves inciais
                        ShipLoader.bindTexture();
                        Point p = ((ListaImagem) lista).getPosition(0);
                        ShipLoader.draw(ShipLoader.DEFAULT_EQUIPS[0], (int) p.x, (int) p.y, ((ListaImagem) lista).getRealWidth(), 0, 0);
                        p = ((ListaImagem) lista).getPosition(1);
                        ShipLoader.draw(ShipLoader.DEFAULT_EQUIPS[1], (int) p.x, (int) p.y, ((ListaImagem) lista).getRealWidth(), 0, 0);
                        p = ((ListaImagem) lista).getPosition(2);
                        ShipLoader.draw(ShipLoader.DEFAULT_EQUIPS[2], (int) p.x, (int) p.y, ((ListaImagem) lista).getRealWidth(), 0, 0);
                    }
                };
                break;
            case 6: //Jogo
                objetosTela = null;
                lista = null;
                break;
        }
    }

    public void checarMovimentos(int delta) {
        while (Keyboard.next()) {
            int key = Keyboard.getEventKey();
            if (Keyboard.getEventKeyState())
                keyPressed(key, Keyboard.getEventCharacter());
            else
                keyReleased(key);
        }
        
        //process pending keys
        Iterator<Integer> it = keyTiming.keySet().iterator();
        while (it.hasNext()) {
            int k = it.next(), c = keyTiming.get(k);
            if (c <= 0) {
                while (c <= -Controls.REPEATING_TIME) {
                    keyPressed(k, Controls.getChar(k));
                    c += Controls.REPEATING_TIME;
                }
                keyTiming.put(k, c - delta);
            } else {
                if (c > Controls.INTERVAL_UNTIL_REPEAT) {
                    keyPressed(k, Controls.getChar(k));
                    keyTiming.put(k, Controls.INTERVAL_UNTIL_REPEAT - c);
                } else
                    keyTiming.put(k, c + delta);
            }
        }
    }

    private void keyPressed(int k, char c) {
        boolean menu = status < 6 || currentMenu != null;
        Key key = Controls.get(k, menu);
        if (menu || key == null || key.isDiscrete())
            keyTiming.put(k, 1); //doesn't add only if not in menu, the key is specified and is not discrete

        if (status < 4) {
            if (key == SEL_UP)
                lista.mover(-1);
            else if (key == SEL_DOWN)
                lista.mover(1);
            else if (key == SEL_GO) {
                if (status == 0)
                    if (lista.getSelecao() == 3)
                        status = -1;
                    else
                        setStatus(lista.getSelecao() + 1);
                else if (status == 1)
                    if (lista.getSelecao() == 0)
                        setStatus(4);
                    else
                        try {
                            gSaves.carregarSave(((ListaTexto) lista).getTexto(lista.getSelecao()));
                            setStatus(6); //THE GAME
                        } catch (IOException e) {
                            System.err.println("Houve um erro ao carregar o jogo. Erro: " + e.getMessage());
                        }
            } else if (key == SEL_BACK)
                if (status != 0)
                    setStatus(0);
        } else if (status == 4) { //Nome: ouvir letras
            CaixaTexto t = (CaixaTexto) objetosTela.get(0);
            if (key == SEL_GO)
                setStatus(5);
            else if (key == SEL_BACK)
                setStatus(1);
            else
                t.parse(k, c);
        } else if (status == 5) { //Nave: lista horizontal
            if (key == SEL_RIGHT) {
                lista.mover(1);
                objetosTela.set(3, new BoxTexto(10, 300, SCREEN_WIDTH - 20, t().SHIPS_DESCRIPTION[lista.getSelecao()]));
            } else if (key == SEL_LEFT) {
                lista.mover(-1);
                objetosTela.set(3, new BoxTexto(10, 300, SCREEN_WIDTH - 20, t().SHIPS_DESCRIPTION[lista.getSelecao()]));
            } else if (key == SEL_GO) {
                //Criar novo jogo
                gSaves.adicionarSave(((Texto) objetosTela.get(2)).getTexto(), lista.getSelecao());
                //Inicia o jogo
                setStatus(6);
            }
        } else //THE GAME
            if (currentMenu == null) {
                if (key == MENU && !gSaves.getSave().inDialog())
                    currentMenu = MenuData.MAIN_MENU.setProfile(gSaves.getProfile()).zerar();
                else {
                    currentMenu = gSaves.getSave().parse(k, c); //the method may receive any menus it opens
                }
            } else {
                if (key == SEL_BACK || key == MENU)
                    currentMenu = currentMenu.back();
                else if (key == SEL_GO)
                    if (currentMenu.getActionCode() == -1) //Ação é um menu
                        currentMenu = currentMenu.go();
                    else
                        switch (currentMenu.getActionCode()) {
                            case 0: //Load
                                //TODO create some sort of dialog
                                try {
                                    gSaves.carregarSave(gSaves.getProfileAtual(), ((SaveLoadMenu) currentMenu).getSave());
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                currentMenu.dispose();
                                currentMenu = null;
                                break;
                            case 1: //Save
                                try {
                                    if (((SimpleMenu) currentMenu).getSelectedItem() == 0)
                                        gSaves.salvarNovo();
                                    else
                                        gSaves.salvar(((SaveLoadMenu) currentMenu).getSave());
                                } catch (IOException e) {
                                    e.printStackTrace(); //todo fancy error logging
                                }
                                currentMenu.dispose();
                                currentMenu = null;
                                break;
                            case 4: //Exit
                                status = -1;
                                break;
                        }
                else
                    currentMenu.parse(k, c);
                if (currentMenu == null) { //exit menu
                    if (gSaves.getSave().getCurrentArea() != null)
                    gSaves.getSave().getCurrentArea().updateShapes();
                }
            }
    }

    private void keyReleased(int k) {
        keyTiming.remove(k); //released, try to remove it
        if (status != 6) //only in-game [todo : so far]
            return;
        
        Key key = Controls.get(k, currentMenu != null);
        if (key == ACCELERATE || key == BREAK)
            gSaves.getSave().setAc(0);
        if (key == TURN_RIGHT || key == TURN_LEFT)
            gSaves.getSave().girar(0);
    }

    public void atualizar(int delta) {
        if (status == 6) {
            if (currentMenu == null)
                gSaves.atualizar(delta);
        } else {
            pf.x += delta * vp;
            if (pf.x > 2 * SCREEN_WIDTH / 3 - ObjectLoader.BACK.getImageWidth() / 2)
                vp = -Math.abs(vp);
            else if (pf.x < SCREEN_WIDTH / 3 - ObjectLoader.BACK.getImageWidth() / 2)
                vp = Math.abs(vp);
        }
    }

    public void desenhar() {
        if (status <= 5) {
            //Desenhar fundo
            ObjectLoader.drawBack((int) pf.x, (int) pf.y);

            //Desenhar objs tela
            for (ObjetoTela obj : objetosTela)
                obj.desenhar();
            if (lista != null)
                lista.desenhar();
        } else if (currentMenu == null)
            gSaves.desenhar();
        else
            currentMenu.draw();
    }

    private int getDelta() {
        int delta = (int) (getTime() - lastFrame);
        lastFrame = getTime();
        return delta;
    }

    public static long getTime() {
        return (Sys.getTime() * 1000) / Sys.getTimerResolution();
    }
}
